#!/bin/bash
#
# Copyright (C) 2011-2020 Aratelia Limited - Juan A. Rubio and contributors
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

#
# Development build script for Tizonia
#

source tizonia-common.inc

##################################################################
# Usage details
#
# Globals
#   None
# Arguments:
#   None
# Returns:
#   None
##################################################################
function usage {
    print_banner_and_license $(basename "$0")
    echo
    pretty_print "$GRN" "Usage : $(basename $0) [-a|--asan] [-b|--debian] [-c|--conf] [-d|--deps] [-e|--bear] [-g|--debug] [-i|--install] [-m|--make] [-n|no-player] [-p|--project <proj_name>] [-q|--quiet] [-r|--release] [-s|--scan-build] [-v|--valgrind] [-w|--cwd]"
    pretty_print "$GRN" "       -a            : Configure for an Address Sanitizer (ASAN) type of build (requires clang)."
    pretty_print "$GRN" "       -b            : Configure for a 'Debian's checkinstall' type build (--libdir=/usr/lib/$(dpkg-architecture -qDEB_HOST_MULTIARCH), then 'checkinstall')."
    pretty_print "$GRN" "       -c            : Configure for OpenMAX IL conformance testing."
    pretty_print "$GRN" "       -d            : Install Tizonia's development dependencies."
    pretty_print "$GRN" "       -e            : Create compilation database (compile_commands.json) using Bear. Expects a fully configured source tree."
    pretty_print "$GRN" "       -g            : Configure for a DEBUG type of build."
    pretty_print "$GRN" "       -i            : Make and install all sub-projects."
    pretty_print "$GRN" "       -m            : Make all sub-projects."
    pretty_print "$GRN" "       -n            : Do not build command-line player program."
    pretty_print "$GRN" "       -p            : Use the '<proj_name>' as the base folder for configuration/build (i.e. to configure/build recursively from that point)."
    pretty_print "$GRN" "       -q            : Run the various tools in a less verbose manner (use with --debug and --release)."
    pretty_print "$GRN" "       -r            : Configure for a RELEASE type of build."
    pretty_print "$GRN" "       -s            : Configure for 'scan-build' (Clang static analyzer)."
    pretty_print "$GRN" "       -v            : Configure for Valgrind memory leak analysis."
    pretty_print "$GRN" "       -w            : Use the the current working directory as the base directory for configuration/build."
    echo
    echo
    print_banner " Typical workflow" "$MAG"
    pretty_print "$CYA" " 1.- Define environment variables needed by this script: "
    pretty_print "$YEL" "     $ export TIZONIA_REPO_DIR=/path/to/tizonia/repo # (e.g. /home/juan/work/tizonia)"
    pretty_print "$YEL" "     $ export TIZONIA_INSTALL_DIR=/path/to/install/basedir # (e.g. /home/juan/temp)"
    pretty_print "$YEL" "     $ export PATH=$TIZONIA_REPO_DIR/tools:$PATH"
    pretty_print "$CYA" " 2.- Install Tizonia's development dependencies: "
    pretty_print "$YEL" "     $ TIZONIA_REPO_DIR=$TIZONIA_REPO_DIR TIZONIA_INSTALL_DIR=$HOME/temp tizonia-dev-build --deps"
    pretty_print "$CYA" " 3.- Recursively configure ('debug' flavor), and then make and install the binaries (from top of Tizonia's repo): "
    pretty_print "$YEL" "     $ TIZONIA_REPO_DIR=$TIZONIA_REPO_DIR TIZONIA_INSTALL_DIR=$HOME/temp tizonia-dev-build --debug --install"
    pretty_print "$CYA" " 4.- Recursively configure ('debug' flavor), and then make and install the binaries (from Tizonia's 'player' sub-dir): "
    pretty_print "$YEL" "     $ TIZONIA_REPO_DIR=$TIZONIA_REPO_DIR TIZONIA_INSTALL_DIR=$HOME/temp tizonia-dev-build --debug --install --project=tizonia-player"
    echo
} >&2

##################################################################
# Function that runs make and make install with a best guess of
# make job count
#
# Globals
#   None
# Arguments:
#   None
# Returns:
#   None
##################################################################
function build_project {
    local install_option=${1:-''}
    local buildlog=$(mktemp)
    local exit_status=0
    local mem=$(free -b | grep Mem | awk '{ print $2 }')
    local bytes_per_job=5368709120 # 5GB, an estimate of how much mem is required per job
    local memjobcnt=$(( mem / bytes_per_job ))
    local cpujobcnt=$(nproc)
    local jobcnt=$(( memjobcnt < cpujobcnt ? memjobcnt : cpujobcnt ))
    local bearcmd='bear'
    local bearopt='--append'

    which bear > /dev/null
    if [[ "$?" -ne 0 ]]; then
        bearcmd='' ; bearopt=''
    fi
    if [[ ! -f compile_commands.json ]]; then
        bearcmd='' ; bearopt=''
    fi

    if [[ -z "$install_option" ]]; then
        print_banner "Building [$TIZONIA_PROJ_NAME] [$jobcnt jobs]" "$GRN"
        eval "$bearcmd" "$bearopt" make -s -j"$jobcnt" V=0 2>&1 | tee "$buildlog"
    else
        print_banner "Installing [$TIZONIA_PROJ_NAME] [$jobcnt jobs]" "$GRN"
        eval "$bearcmd" "$bearopt" make -s -j"$jobcnt" V=0 2>&1 install | tee "$buildlog"
    fi
    exit_status="${PIPESTATUS[0]}"
    if [[ "$exit_status" != 0 ]]; then
        if [[ -z "$install_option" ]]; then
            print_banner "Building [$TIZONIA_PROJ_NAME] [2nd pass, 1 job]" "$GRN"
            eval "$bearcmd" "$bearopt" make -s -j1 V=0 | tee -a "$buildlog"
        else
            print_banner "Installing [$TIZONIA_PROJ_NAME] [2nd pass, 1 job]" "$GRN"
            eval "$bearcmd" "$bearopt" make -s -j1 V=0 install | tee -a "$buildlog"
        fi
        exit_status="$?"
    fi
    rm -f "$buildlog"
    return "$exit_status"
}

##################################################################
# Main function
#
# Globals
#   TIZONIA_DIR
#   DEBIAN_DIR
# Arguments:
#   None
# Returns:
#   None
##################################################################
function main {
    local exit_status=0
    local asan=0
    local debian=0
    local beardb=0
    local conf=0
    local deps=0
    local project=0
    local quiet=0
    local proj_name=""
    local debug=0
    local install=0
    local make=0
    local player=( --enable-player )
    local release=0
    local scan=0
    local valgrind=0
    local cwd=0

    local progname=$(basename $0)
    CMDLINE=$(getopt -o "abcdegimnp:qrsvw" --long "asan,debian,conf,deps,bear,debug,install,make,no-player,project:,quiet,release,scan-build,valgrind,cwd," -n "$progname" -- "$@")
    eval set -- "$CMDLINE"
    while true; do
        case "$1" in
            -a|--asan)
                asan=1; shift
                ;;
            -b|--debian)
                debian=1; shift
                ;;
            -c|--conf)
                conf=1; shift
                ;;
            -d|--deps)
                deps=1; shift
                ;;
            -e|--bear)
                beardb=1; shift
                ;;
            -g|--debug)
                debug=1; shift
                ;;
            -i|--install)
                install=1; shift
                ;;
            -m|--make)
                make=1; shift
                ;;
            -n|--no-player)
                player=( --disable-player ); shift
                ;;
            -p|--project)
                project=1;
                shift
                if [[ -n "$1" ]]; then
                    proj_name="$1"
                    shift
                fi
                ;;
            -q|--quiet)
                quiet=1; shift
                ;;
            -r|--release)
                release=1; shift
                ;;
            -s|--scan-build)
                scan=1; shift
                ;;
            -v|--valgrind)
                valgrind=1; shift
                ;;
            -w|--cwd)
                cwd=1; shift
                ;;
            --)
                shift
                break
                ;;
        esac
    done

    set -o nounset
    set -o pipefail

    if [[ ("$deps" == 1) ]]; then
        tizonia-dpkg-build --tizdeps
        exit $?
    fi

    local make_install_total=$(( $make + $install ))

    local config_total=$(( $asan + $debian + $conf + $debug + $beardb + $release + $scan + $valgrind ))
    if [[ ("$config_total" != 1) && ("$make_install_total" == 0) ]]; then
        usage
        exit "$E_BADARGS"
    fi

    if [[ ("$config_total" == 0) && ("$make_install_total" != 1) ]]; then
        usage
        exit "$E_BADARGS"
    fi

    # Verify the existence of important environment variables
    : ${TIZONIA_REPO_DIR:?"Need to set TIZONIA_REPO_DIR"}
    : ${TIZONIA_INSTALL_DIR:?"Need to set TIZONIA_INSTALL_DIR"}

    print_banner_and_license $(basename "$0")

    LIB_DIR="$TIZONIA_INSTALL_DIR"/lib
    INCLUDE_DIR="$TIZONIA_INSTALL_DIR"/include
    PKG_CONFIG_DIR="$LIB_DIR"/pkgconfig

    print_banner "Exporting influential environment variables" "$GRN"
    pretty_print "$BLU" "LD_LIBRARY_PATH=$LIB_DIR"
    pretty_print "$BLU" "LDFLAGS=-L$LIB_DIR"
    pretty_print "$BLU" "PKG_CONFIG_PATH=$PKG_CONFIG_DIR"
    export LD_LIBRARY_PATH="$LIB_DIR"
    export LDFLAGS="-L$LIB_DIR"
    export PKG_CONFIG_PATH="$PKG_CONFIG_DIR"
    which ccache &> /dev/null
    if [[ $? -eq 0 ]]; then
        pretty_print "$BLU" "Adding ccache symlinks to PATH (/usr/lib/ccache)"
        pretty_print "$BLU" 'PATH=/usr/lib/ccache:$PATH'
        export PATH=/usr/lib/ccache:$PATH
    fi

    local builddir="$TIZONIA_REPO_DIR"
    if [[ "$project" == 1 ]]; then
        exists_in_array "$proj_name" "${TIZ_PROJECTS_ORDERED[@]}"
        if [[ $? -ne 0 ]]; then
            echo
            pretty_print "$RED" "[$proj_name] : Unknown project name."
            echo
            pretty_print "$MAG" "Current list of available projects:"
            for proj in "${TIZ_PROJECTS_ORDERED[@]}"; do
                local proj_dir="$TIZONIA_REPO_DIR/${TIZ_PROJECT_DIRS[$proj]}"
                pretty_print "$GRN" "[$proj] : $proj_dir"
            done
            echo
            raise_error "Please review this list and provide a valid project name."
        else
            builddir="$TIZONIA_REPO_DIR/${TIZ_PROJECT_DIRS[$proj_name]}"
        fi
    elif [[ "$cwd" == 1 ]]; then
        builddir="$PWD"
        proj_name="Tizonia"
    else
        proj_name="Tizonia"
    fi

    export TIZONIA_PROJ_NAME="$proj_name"
    export TIZONIA_BUILD_DIR="$builddir"

    echo
    pretty_print "$MAG" "Changing dir to $builddir"
    cd "$builddir"

    local config=1
    if [[ ("$make" == 1) || ("$install" == 1) ]]; then
        config=0
    fi

    local before="$(date +%s)"

    if [[ ("$debug" == 1) ]]; then

        local debug=(
            --prefix=$TIZONIA_INSTALL_DIR \
            --with-bashcompletiondir=$TIZONIA_INSTALL_DIR/share/bash-completion/completions \
            --with-zshcompletiondir=$TIZONIA_INSTALL_DIR/share/zsh/site-functions \
            --enable-test \
            )

        print_banner "Reconfiguring [$TIZONIA_PROJ_NAME] for [DEBUG] build - Install dir [$TIZONIA_INSTALL_DIR]" "$YEL"
        if [[ ("$quiet" == 1) ]]; then
            autoreconf -ifsv 2>&1 | grep --color=always '.*Entering directory' \
                && ./configure \
                       --enable-silent-rules \
                       ${debug[@]} \
                       ${player[@]} \
                       CFLAGS="-O0 -ggdb -Wall -I$INCLUDE_DIR" \
                       CXXFLAGS='-O0 -ggdb -Wall -I$INCLUDE_DIR' \
                    | grep --color=always '=== configuring in'
        else
            autoreconf -ifs \
                && ./configure ${debug[@]} ${player[@]} CFLAGS='-O0 -ggdb -Wall -I$INCLUDE_DIR' CXXFLAGS='-O0 -ggdb -Wall -I$INCLUDE_DIR'
        fi

    elif [[ ("$release" == 1) ]];  then

        local release=(
            --prefix=$TIZONIA_INSTALL_DIR \
            --with-bashcompletiondir=$TIZONIA_INSTALL_DIR/share/bash-completion/completions \
            --with-zshcompletiondir=$TIZONIA_INSTALL_DIR/share/zsh/site-functions \
            )

        print_banner "Reconfiguring [$TIZONIA_PROJ_NAME] for [RELEASE] build - Install dir [$TIZONIA_INSTALL_DIR]..." "$YEL"
        if [[ ("$quiet" == 1) ]]; then
            autoreconf -ifsv 2>&1 | grep --color=always '.*Entering directory' \
                && ./configure \
                       --enable-silent-rules \
                       ${release[@]} \
                       ${player[@]} \
                       CFLAGS='-O2 -s -DNDEBUG -I$INCLUDE_DIR'  \
                       CXXFLAGS='-O2 -s -DNDEBUG -fstack-protector --param=ssp-buffer-size=4 -Wformat -Werror=format-security -I$INCLUDE_DIR' \
                    | grep --color=always '=== configuring in'
        else
            autoreconf -ifs \
                && ./configure ${release[@]} ${player[@]} CFLAGS='-O2 -s -DNDEBUG -I$INCLUDE_DIR'  \
                               CXXFLAGS='-O2 -s -DNDEBUG -fstack-protector --param=ssp-buffer-size=4 -Wformat -Werror=format-security -I$INCLUDE_DIR'
        fi

    elif [[ ("$asan" == 1) ]]; then

        print_banner "Reconfiguring [$TIZONIA_PROJ_NAME] for [ASAN] build - Install dir [$TIZONIA_INSTALL_DIR]..." "$YEL"
        autoreconf -ifs \
            && ./configure \
            --silent \
            --enable-silent-rules \
            --with-bashcompletiondir="$TIZONIA_INSTALL_DIR"/share/bash-completion/completions \
            --with-zshcompletiondir="$TIZONIA_INSTALL_DIR"/share/zsh/site-functions \
            "${player[@]}" \
            --prefix="$TIZONIA_INSTALL_DIR" \
            CFLAGS='-ggdb -Wall -Werror -fno-omit-frame-pointer -fsanitize=address -I$INCLUDE_DIR' \
            CXXFLAGS='-ggdb -Wall -fno-omit-frame-pointer -fsanitize=address -I$INCLUDE_DIR' \
            LDFLAGS='-fsanitize=address'

    elif [[ ("$debian" == 1) ]]; then

        print_banner "Reconfiguring [$TIZONIA_PROJ_NAME] for [Debian] build - Install dir [/usr]..." "$YEL"
        autoreconf -ifs \
            && ./configure \
            --silent \
            --enable-silent-rules \
            --with-bashcompletiondir \
            --with-zshcompletiondir \
            "${player[@]}" \
            --prefix=/usr \
            --libdir=/usr/lib/$(dpkg-architecture -qDEB_HOST_MULTIARCH) \
            CFLAGS='-O2 -s -DNDEBUG -I$INCLUDE_DIR'  \
            CXXFLAGS='-O2 -s -DNDEBUG -fstack-protector --param=ssp-buffer-size=4 -Wformat -Werror=format-security -I$INCLUDE_DIR'

        if [[ "$?" != 0 ]]; then
            raise_error "Configuration failure."
        fi

        make
        if [[ "$?" != 0 ]]; then
            raise_error "Build failure."
        fi

        sudo checkinstall \
             --nodoc \
             --pkgname tizonia-all-testing \
             --pkgversion 0.11.0d \
             --maintainer='Juan\ A.\ Rubio\ \<jarubio2001@gmail.com\>'
        if [[ "$?" != 0 ]]; then
            raise_error "checkinstall failure."
        fi

        exit 0

    elif [[ ("$beardb" == 1) ]]; then

        print_banner "Generating compilation dabatase and initial indexing for [RTAGS]..." "$YEL"
        pretty_print "$MAG" "Cleaning source tree..."
        make clean > /dev/null \
            && pretty_print "$MAG" "Generating json compilation database using 'bear' ('bear' should have been installed already)..." \
            && bear make -s \
            && pretty_print "$MAG" "Indexing the source tree (the rtags is supposed to be installed and running at this point)" \
            && rc -J .

    elif [[ ("$conf" == 1)  ]];  then

        print_banner "Reconfiguring [$TIZONIA_PROJ_NAME] for [CONFORMANCE TESTING] build - Install dir [$TIZONIA_INSTALL_DIR]..." "$YEL"
        autoreconf -ifs \
            && ./configure \
            --enable-silent-rules \
            "${player[@]}" \
            --enable-blocking-etb-ftb \
            --prefix="$TIZONIA_INSTALL_DIR" \
            CFLAGS='-ggdb -Wall -Werror -I$INCLUDE_DIR'

    elif [[ ("$valgrind" == 1)  ]];  then

        print_banner "Reconfiguring [$TIZONIA_PROJ_NAME] for [VALGRIND] build - Install dir [$TIZONIA_INSTALL_DIR]..." "$YEL"
        autoreconf -ifs \
        && ./configure \
            --silent \
            --enable-silent-rules \
            "${player[@]}" \
            --prefix="$TIZONIA_INSTALL_DIR" \
            CFLAGS='-O0 -g -I$INCLUDE_DIR' \
            CXXFLAGS='-O0 -g -I$INCLUDE_DIR'

    elif [[ ("$scan" == 1) ]]; then

        print_banner "Reconfiguring [$TIZONIA_PROJ_NAME] for [SCAN-BUILD] build - Install dir [$TIZONIA_INSTALL_DIR]..." "$YEL"
        scan-build autoreconf -ifs \
            && scan-build ./configure \
                          "${player[@]}" \
                          --prefix="$TIZONIA_INSTALL_DIR" \
                          CFLAGS='-ggdb -Wall -Werror -I$INCLUDE_DIR'

    fi

    if [[ "$?" != 0 ]]; then
        raise_error "Configuration failure."
    fi

    if [[ ("$scan" == 1) && ("$make" == 1) ]]; then
        print_banner "Building for [SCAN-BUILD] build" "$GRN"
        scan-build make
    elif [[ ("$valgrind" == 1) && ( ("$make" == 1) || ("$install" == 1) ) ]]; then
        print_banner "Building for [VALGRIND] build" "$GRN"
        make CFLAGS='-O0 -g -I$INCLUDE_DIR' CXXFLAGS='-O0 -g -I$INCLUDE_DIR'
    elif [[ ("$make" == 1) ]]; then
        build_project
    fi

    if [[ "$?" != 0 ]]; then
        echo "raise error"
        raise_error "*** make failed ***"
    fi

    if [[ "$install" == 1 ]]; then
        build_project "install"
    fi

    if [[ "$?" != 0 ]]; then
        echo "raise error"
        raise_error "*** make install failed ***"
    fi

    local after="$(date +%s)"
    local elapsed_seconds="$(expr $after - $before)"
    print_banner "Elapsed time: $(date -d\@$elapsed_seconds -u +%H:%M:%S)" "$BLU"

    exit "$?"
}

main "$@"
